#![allow(unused)]

use std::collections::HashMap;
use std::sync::Arc;

use thiserror::Error;

use crate::asset;
use crate::intent;
use crate::network;
use crate::resource;
use crate::resource::Resource;

pub struct IntentRegistry {
    executors: HashMap<intent::IntentId, intent::ExecutorRef>,
    next_intent_id: u64,
}

impl IntentRegistry {
    pub fn register_executor(&mut self, exec: intent::ExecutorRef) -> intent::IntentId {
        let id = self.next_intent_id.into();
        self.next_intent_id += 1;
        self.executors.insert(id, exec);
        id
    }
}

#[derive(Clone)]
pub enum PropMatch {
    /// Concrete value property match.
    Value(resource::Prop),

    /// Indexed parameter.
    Param(u16),
}

#[derive(Clone)]
pub enum ResSelector {
    /// Resource must match all of these selectors.
    And(Vec<ResSelector>),

    /// Genericizes properties of the given type.
    ForProp(u16, resource::PropTy, Box<ResSelector>),

    /// Always matches.
    True,

    /// Always doesn't match.
    False,

    /// If the resource has this property.
    HasProperty(PropMatch),

    /// If the resource is *at least* this amount of this property, if this
    /// properties can be compared using `>`.  Always false otherwise.
    HasPropertyMin(PropMatch),
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("unspecified: {0}")]
    Unspecified(String),
}

enum Bind<'ctx> {
    Base,
    ForProp(u16, resource::Prop, &'ctx Bind<'ctx>),
}

fn lookup_bind<'b>(i: u16, bind: &'b Bind<'b>) -> Option<&'b resource::Prop> {
    match bind {
        Bind::Base => None,
        Bind::ForProp(j, p, b) => {
            if i == *j {
                Some(p)
            } else {
                lookup_bind(i, *b)
            }
        }
    }
}

fn check_resource_selector(sel: &ResSelector, res: &Resource) -> Result<bool, Error> {
    check_resource_selector_inner(sel, res, &Bind::Base)
}

fn check_resource_selector_inner<'b>(
    sel: &ResSelector,
    res: &Resource,
    bind: &Bind<'b>,
) -> Result<bool, Error> {
    use ResSelector::*;
    match sel {
        And(sels) => {
            for sel in sels {
                if !check_resource_selector_inner(sel, res, bind)? {
                    return Ok(false);
                }
            }
            Ok(true)
        }

        ForProp(i, prop, expr) => {
            let pv = match res.get_prop_by_ty(*prop) {
                Some(v) => v,
                _ => return Ok(false),
            };

            let nbind = Bind::ForProp(*i, pv, &bind);
            check_resource_selector_inner(expr, res, &nbind)
        }

        True => Ok(true),

        False => Ok(false),

        HasProperty(pm) => match pm {
            PropMatch::Param(i) => {
                let pv = match lookup_bind(*i, bind) {
                    Some(v) => v,
                    _ => return Ok(false),
                };

                let rpv = match res.get_prop_by_ty(pv.ty()) {
                    Some(v) => v,
                    _ => return Ok(false),
                };

                Ok(*pv == rpv)
            }

            PropMatch::Value(pv) => {
                let rpv = match res.get_prop_by_ty(pv.ty()) {
                    Some(v) => v,
                    _ => return Ok(false),
                };

                Ok(*pv == rpv)
            }
        },

        HasPropertyMin(pm) => match pm {
            PropMatch::Param(i) => {
                let pv = match lookup_bind(*i, bind) {
                    Some(v) => v,
                    _ => return Ok(false),
                };

                let rpv = match res.get_prop_by_ty(pv.ty()) {
                    Some(v) => v,
                    _ => return Ok(false),
                };

                match (pv, rpv) {
                    (resource::Prop::Amount(pv), resource::Prop::Amount(rpv)) => Ok(rpv > *pv),
                    _ => Ok(false),
                }
            }

            PropMatch::Value(pv) => {
                let rpv = match res.get_prop_by_ty(pv.ty()) {
                    Some(v) => v,
                    _ => return Ok(false),
                };

                match (pv, rpv) {
                    (resource::Prop::Amount(pv), resource::Prop::Amount(rpv)) => Ok(rpv > *pv),
                    _ => Ok(false),
                }
            }
        },

        _ => unimplemented!(),
    }
}
